﻿using System;
using MugenMvvmToolkit.Interfaces;
using MugenMvvmToolkit.Utils;
#if TASKSUPPORT
using System.Threading.Tasks;
#else
using MugenMvvmToolkit.Infrastructure;

#endif

namespace MugenMvvmToolkit.Models
{
    /// <summary>
    ///     Represents the interface that contains a result of the navigation operation.
    /// </summary>
    public class NavigationResult : INavigationResult
    {
        #region Common fields

        /// <summary>
        ///     Gets the navigation result with true navigation value.
        /// </summary>
        public static readonly NavigationResult TrueNavigationResult;

        #endregion

        #region Constructors

        /// <summary>
        ///     Initializes a new instance of the <see cref="NavigationResult" /> class.
        /// </summary>
        static NavigationResult()
        {
            TrueNavigationResult = new NavigationResult(true, null);
        }

        #endregion

        #region Common properties

        /// <summary>
        ///     Gets or sets value that indicates that current result is attached to events, this flag is used to create operation
        ///     result callback.
        /// </summary>
        public bool IsAttached { get; set; }

        #endregion

#if TASKSUPPORT
        #region Fields

        private readonly TaskCompletionSource<bool> _navigationResultTask;

        private readonly TaskCompletionSource<bool?> _operationResultTask;

        #endregion

        #region Constructors

        /// <summary>
        ///     Initializes a new instance of the <see cref="NavigationResult" /> class.
        /// </summary>
        public NavigationResult()
            : this(new TaskCompletionSource<bool>(), new TaskCompletionSource<bool?>())
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="NavigationResult"/> class.
        /// </summary>
        public NavigationResult(TaskCompletionSource<bool> navigationResultTask, TaskCompletionSource<bool?> operationResultTask)
        {
            Should.NotBeNull(navigationResultTask, "navigationResultTask");
            Should.NotBeNull(operationResultTask, "operationResultTask");
            _navigationResultTask = navigationResultTask;
            _operationResultTask = operationResultTask;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="NavigationResult"/> class.
        /// </summary>
        public NavigationResult(bool navigationResult, bool? operationResult)
        {
            _navigationResultTask = new TaskCompletionSource<bool>();
            _navigationResultTask.SetResult(navigationResult);

            _operationResultTask = new TaskCompletionSource<bool?>();
            _operationResultTask.SetResult(operationResult);
        }

        #endregion

        #region Properties

        /// <summary>
        ///     Gets the navigation result task, this task returns the result of the navigation operation.
        /// </summary>
        public TaskCompletionSource<bool> NavigationResultTask
        {
            get { return _navigationResultTask; }
        }

        /// <summary>
        ///     Gets the navigation result task, this task returns the result of the operation.
        /// </summary>
        public TaskCompletionSource<bool?> OperationResultTask
        {
            get { return _operationResultTask; }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Tries to complete navigation task.
        /// </summary>
        /// <param name="task">An instance of task.</param>
        /// <param name="result">The result value, if any</param>
        /// <param name="currentContext">The current navigation context.</param>
        public void TryCompleteNavigation(Task task, bool? result, ref INavigationContext currentContext)
        {
            var activeNavigationTask = NavigationResultTask;
            if (activeNavigationTask.Task.IsCompleted)
                return;
            if (task != null)
            {
                if (task.IsFaulted)
                {
                    currentContext = null;
                    activeNavigationTask.TrySetException(task.Exception);
                    TryCompleteOperation(false, null, task.Exception, false);
                }
                if (task.IsCanceled)
                {
                    currentContext = null;
                    TryCompleteOperation(false, null, null, true);
                    activeNavigationTask.TrySetCanceled();
                }
            }
            if (result != null)
            {
                activeNavigationTask.TrySetResult(result.Value);
                if (!result.Value)
                    TryCompleteOperation(true, null, null, false);
            }
        }

        /// <summary>
        /// Tries to complete operation task.
        /// </summary>
        /// <param name="setResult">The value to set result.</param>
        /// <param name="result">The result value.</param>
        /// <param name="exception">An operation exception.</param>
        public void TryCompleteOperation(bool setResult, bool? result, Exception exception)
        {
            TryCompleteOperation(setResult, result, exception, false);
        }

        private void TryCompleteOperation(bool setResult, bool? result, Exception exception, bool canceled)
        {
            var activeOperation = OperationResultTask;
            if (activeOperation.Task.IsCompleted)
                return;
            if (exception != null)
                activeOperation.TrySetException(exception);
            if (canceled)
                activeOperation.TrySetCanceled();
            if (setResult)
                activeOperation.TrySetResult(result);
        }

        #endregion

        #region Implementation of INavigationResult

        /// <summary>
        ///     Gets the navigation result task, this task returns the result of the operation.
        /// </summary>
        Task<bool?> INavigationResult.OperationResultTask
        {
            get { return OperationResultTask.Task; }
        }

        /// <summary>
        ///     Gets the navigation result task, this task returns the result of the navigation operation.
        /// </summary>
        Task<bool> INavigationResult.NavigationResultTask
        {
            get { return NavigationResultTask.Task; }
        }

        #endregion

#else

        #region Fields

        private readonly ManualOperationResult<bool> _navigationResultTask;
        private readonly ManualOperationResult<bool?> _operationResultTask;

        #endregion

        #region Constructors

        /// <summary>
        ///     Initializes a new instance of the <see cref="NavigationResult" /> class.
        /// </summary>
        public NavigationResult()
            : this(new ManualOperationResult<bool>(), new ManualOperationResult<bool?>())
        {
        }

        /// <summary>
        ///     Initializes a new instance of the <see cref="NavigationResult" /> class.
        /// </summary>
        public NavigationResult(ManualOperationResult<bool> navigationResultTask,
            ManualOperationResult<bool?> operationResultTask)
        {
            Should.NotBeNull(navigationResultTask, "navigationResultTask");
            Should.NotBeNull(operationResultTask, "operationResultTask");
            _navigationResultTask = navigationResultTask;
            _operationResultTask = operationResultTask;
        }

        /// <summary>
        ///     Initializes a new instance of the <see cref="NavigationResult" /> class.
        /// </summary>
        public NavigationResult(bool navigationResult, bool? operationResult)
        {
            _navigationResultTask = new ManualOperationResult<bool>();
            _navigationResultTask.SetResult(navigationResult);

            _operationResultTask = new ManualOperationResult<bool?>();
            _operationResultTask.SetResult(operationResult);
        }

        #endregion

        #region Properties

        /// <summary>
        ///     Gets the navigation result task, this task returns the result of the navigation operation.
        /// </summary>
        public ManualOperationResult<bool> NavigationResultTask
        {
            get { return _navigationResultTask; }
        }

        /// <summary>
        ///     Gets the navigation result task, this task returns the result of the operation.
        /// </summary>
        public ManualOperationResult<bool?> OperationResultTask
        {
            get { return _operationResultTask; }
        }

        #endregion

        #region Methods

        /// <summary>
        ///     Tries to complete navigation task.
        /// </summary>
        /// <param name="task">An instance of task.</param>
        /// <param name="result">The result value, if any</param>
        /// <param name="currentContext">The current navigation context.</param>
        public void TryCompleteNavigation(IOperationResult task, bool? result, ref INavigationContext currentContext)
        {
            ManualOperationResult<bool> activeNavigationTask = NavigationResultTask;
            if (activeNavigationTask.IsCompleted)
                return;
            if (task != null)
            {
                if (task.IsFaulted)
                {
                    currentContext = null;
                    activeNavigationTask.TrySetException(task.Exception);
                    TryCompleteOperation(false, null, task.Exception);
                }
            }
            if (result != null)
            {
                activeNavigationTask.TrySetResult(result.Value);
                if (!result.Value)
                    TryCompleteOperation(true, null, null);
            }
        }

        /// <summary>
        ///     Tries to complete operation task.
        /// </summary>
        /// <param name="setResult">The value to set result.</param>
        /// <param name="result">The result value.</param>
        /// <param name="exception">An operation exception.</param>
        public void TryCompleteOperation(bool setResult, bool? result, Exception exception)
        {
            ManualOperationResult<bool?> activeOperation = OperationResultTask;
            if (activeOperation.IsCompleted)
                return;
            if (exception != null)
                activeOperation.TrySetException(exception);
            if (setResult)
                activeOperation.TrySetResult(result);
        }

        #endregion

        #region Implementation of INavigationResult

        /// <summary>
        ///     Gets the navigation result task, this task returns the result of the operation.
        /// </summary>
        IOperationResult<bool?> INavigationResult.OperationResultTask
        {
            get { return OperationResultTask; }
        }

        /// <summary>
        ///     Gets the navigation result task, this task returns the result of the navigation operation.
        /// </summary>
        IOperationResult<bool> INavigationResult.NavigationResultTask
        {
            get { return NavigationResultTask; }
        }

        #endregion

#endif
    }
}